﻿using System;
using System.Collections.Generic;
using System.Linq;
using ControleRepresentante.Common;
using ControleRepresentanteSync.Application.Wizard;
using ControleRepresentanteSync.ControleRepresentanteServices;
using ControleRepresentanteSync.Logic;

namespace ControleRepresentanteSync.Application.SimpleSync
{
    abstract class SimpleSyncSelectionPresenter<TSelectionViewModel, TItem, TContract, TModel, TContentViewModel, TFullContract, TCodigoContract> : WizardPagePresenterBase<TSelectionViewModel>
        where TSelectionViewModel : ISimpleSyncSelectionViewModel<TContentViewModel, TModel, TItem, TContract>
        where TContentViewModel : class, ISimpleSyncContentViewModel<TModel, TItem, TContract>
        where TItem : ISimpleSycItem<TContract>
    {
        protected IContractsProvider ContractsProvider { get; private set; }
        protected IViewModelFactory ViewModelFactory { get; private set; }

        private readonly IWindow m_Window;
        private readonly IUserInterfaceService m_UserInterfaceService;
        private readonly ISyncServiceProvider m_SyncServiceProvider;
        private readonly List<TItem> m_Items;
        private readonly List<TContentViewModel> m_AllToSync;

        protected SimpleSyncSelectionPresenter(IWindow window, TSelectionViewModel viewModel, IJolitexRetriever jolitexRetriever, IContractsProvider contractsProvider, IViewModelFactory viewModelFactory, IUserInterfaceService userInterfaceService, ISyncServiceProvider syncServiceProvider) 
            : base(viewModel)
        {
            ContractsProvider = contractsProvider;
            ViewModelFactory = viewModelFactory;
            m_Window = window;
            m_UserInterfaceService = userInterfaceService;
            m_SyncServiceProvider = syncServiceProvider;
            
            m_Items = new List<TItem>();

            TItem selectItem = viewModelFactory.CreateViewModel<TItem>();
            PopulateSelectItem(selectItem);
            m_Items.Add(selectItem);

            foreach (TContract contract in GetContracts(contractsProvider))
            {
                TItem item = viewModelFactory.CreateViewModel<TItem>();
                CreateItemPresenter(item, contract);
                m_Items.Add(item);
            }

            IEnumerable<TModel> models = GetModels(jolitexRetriever);
            m_AllToSync = new List<TContentViewModel>();
            foreach (TModel model in models)
            {
                TContentViewModel itemContentViewModel = viewModelFactory.CreateViewModel<TContentViewModel>();
                itemContentViewModel.IsCheckedChanged += ContentViewModelIsCheckedChanged;
                itemContentViewModel.IsValidChanged += ContentViewModelIsValidChanged;
                CreateContentPresenter(itemContentViewModel, model, m_Items);
                m_AllToSync.Add(itemContentViewModel);
            }

            SetToSync(x => true);

            ViewModel.IsAllCheckedChanged += ViewModelIsAllCheckedChanged;
            
            UpdateIsValid();
        }

        protected void SetToSync(Predicate<TContentViewModel> predicate)
        {
            ViewModel.ToSync.Clear();
            foreach (TContentViewModel contentViewModel in m_AllToSync.Where(contentViewModel => predicate(contentViewModel)))
            {
                ViewModel.ToSync.Add(contentViewModel);
            }
        }

        private void ViewModelIsAllCheckedChanged(object sender, EventArgs e)
        {
            bool isAllChecked = ViewModel.IsAllChecked;
            foreach (TContentViewModel contentViewModel in ViewModel.ToSync)
            {
                contentViewModel.IsChecked = isAllChecked;
            }
        }

        protected abstract void CreateContentPresenter(TContentViewModel itemContentViewModel, TModel model, IEnumerable<TItem> items);
        protected abstract IEnumerable<TModel> GetModels(IJolitexRetriever jolitexRetriever);
        protected abstract void CreateItemPresenter(TItem item, TContract contract);
        protected abstract IEnumerable<TContract> GetContracts(IContractsProvider contractsProvider);
        protected abstract void PopulateSelectItem(TItem selectItem);
        protected abstract void SynchronizeCodigo(SyncService syncService, TCodigoContract[] codigoContracts);
        protected abstract void SynchronizeFull(SyncService syncService, TFullContract[] fullContracts);
        protected abstract TCodigoContract ConvertToCodigoContract(TContentViewModel contentViewModel);
        protected abstract TFullContract ConvertToFullContract(TContentViewModel contentViewModel);

        private void ContentViewModelIsValidChanged(object sender, EventArgs e)
        {
            UpdateIsValid();
        }

        private void UpdateIsValid()
        {
            IEnumerable<TContentViewModel> clienteViewModels = m_AllToSync.Where(x => x.IsChecked);
            ViewModel.IsValid = clienteViewModels.Any() && clienteViewModels.All(x => x.IsValid);
        }

        private void ContentViewModelIsCheckedChanged(object sender, EventArgs e)
        {
            UpdateIsValid();
        }

        protected sealed override bool TryApply()
        {
            bool result = false;
            m_UserInterfaceService.ExecuteActionWithWaitMessage("Sincronizando", m_Window, () => m_UserInterfaceService.InvokeOnUIThread(() =>
            {
                try
                {
                    IEnumerable<TContentViewModel> checkedContents = m_AllToSync.Where(x => x.IsChecked);
                    
                    IEnumerable<TFullContract> fullSync = checkedContents
                        .Where(x => x.Action == SyncAction.CreateNew)
                        .Select(ConvertToFullContract);

                    IEnumerable<TCodigoContract> mergeSync = checkedContents
                        .Where(x => x.Action == SyncAction.Merge)
                        .Select(ConvertToCodigoContract);

                    fullSync.ExecuteBatch(20, x => m_SyncServiceProvider.Execute(syncService => SynchronizeFull(syncService, x.ToArray())));
                    mergeSync.ExecuteBatch(20, x => m_SyncServiceProvider.Execute(syncService => SynchronizeCodigo(syncService, x.ToArray())));

                    result = true;

                    OnAfterSuccessfulTryApply(fullSync, mergeSync);
                }
                catch
                {
                    result = false;
                }
            }));
            return result;
        }

        protected abstract void OnAfterSuccessfulTryApply(IEnumerable<TFullContract> fullSync, IEnumerable<TCodigoContract> mergeSync);

        public override void Dispose()
        {
            base.Dispose();

            ViewModel.IsAllCheckedChanged -= ViewModelIsAllCheckedChanged;

            foreach (TContentViewModel contentViewModel in m_AllToSync)
            {
                contentViewModel.IsCheckedChanged -= ContentViewModelIsCheckedChanged;
                contentViewModel.IsValidChanged -= ContentViewModelIsValidChanged;
                contentViewModel.FireOnClose();
            }
            foreach (TItem item in m_Items)
            {
                item.FireOnClose();
            }
        }
    }
}
